<!-- 

如果只需要一个值的双向绑定，那么在父组件使用时，可以直接使用v-model="xxx"或v-model:id="xxx"都可以；
如果需要2个值的双向绑定，那么必须使用 v-model:id="xxx"  v-model:name="xxx" 

如果要可创建条目，则需要加上 allow-create 这个属性

注：在可创建条目的情况下，有一个小问题，复现方法：正常选择一条已经存在的数据，然后鼠标不要点其它地方，还直接点下拉列表，然后输入内容，这时第一次输入的并没有用，连着第二次输入就可以用了
但，如果在选择后，鼠标在别的地方点一下，再点回下拉列表进行输入就没有问题，而且如果是输入完按回车，也一直都没有问题
通过打log看执行的方法，出错的时候，并不会执行onChange或onBlur，只执行了一个onVisibleChange
-->

<template>
    <div class="DataSelect">
        <el-select v-model="vModelData" :placeholder="placeholder" :filterable="filterable" :clearable="clearable" :default-first-option="defaultFirstOption" @change="onChange" @blur="onBlur" @visible-change="onVisibleChange" @clear="onClear" v-bind="$attrs">
            <el-option v-for="item in list" :key="item[value]" :label="item[label]" :value="item[value]"></el-option>
        </el-select>
    </div>
</template>

<script lang="ts" setup>
import _ from "lodash";

const props = defineProps({
    //双向绑定的的id和name
    id: [Number, Array, String],
    name: [String, Array],
    obj: null,

    //表名
    table: {
        type: String,
    },

    //下拉列表显示名称
    label: {
        type: String,
        default: "name",
    },

    //下拉列表选择的值
    value: {
        type: String,
        default: "autoID",
    },

    //提示信息
    placeholder: {
        type: String,
        default: "请选择",
    },

    //接口方法名
    action: {
        type: String,
        default: "GetSearchWithIsDelete",
    },

    //接口方法是否为函数方法
    actionIsFunc: {
        type: Boolean,
        default: true,
    },

    //排序
    orderBy: {
        type: String,
        default: "",
    },

    //搜索条件
    filter: {
        type: Object,
        default: {},
    },

    //是否自动加载数据
    isLoadingData: {
        type: Boolean,
        default: true,
    },

    //是否可搜索
    filterable: {
        type: Boolean,
        default: true,
    },

    //是否可清空
    clearable: {
        type: Boolean,
        default: true,
    },

    //在输入框按下回车，选择第一个匹配项
    defaultFirstOption: {
        type: Boolean,
        default: true,
    },
    distinct: {
        type: Boolean,
        default: false,
    },
});

//下拉列表中保存的数据
const list = ref<Array<any>>([]);

//下拉列表v-model绑定的值
const vModelData = ref();

//$attrs的值
const attrs = useAttrs();

//用来记录各种选择方式选择出来的值，从选择开始到emit之前，都用这个对象来处理
const selectData = reactive({
    id: null,
    name: null,
    obj: null,
});

//组件是否可以添加条目
let _allowCreate = attrs["allow-create"] == "" || attrs["allow-create"] == true;
//组件是否可以多选
let _multiple = attrs.multiple == "" || attrs.multiple == true;
//选择过的旧值，用来对比选择的值有没有改变
let _oldSelectData = null;

//监听属性id的，用于回显时默认选择，给vModelData赋值
watch(
    () => props.id,
    (newVal, oldVal) => {
        if (!Gx.IsEmpty(newVal)) {
            if (_multiple) {
                vModelData.value = newVal;

                let res = [];
                vModelData.value.forEach((x) => {
                    var findItem = list.value.find((item) => {
                        return item[props.value] === x;
                    });

                    if (!Gx.IsEmpty(findItem)) {
                        res.push(findItem);
                    }
                });

                if (res.length > 0) {
                    let ids = res.map((x) => {
                        return x[props.value];
                    });
                    let names = res.map((x) => {
                        return x[props.label];
                    });

                    selectData.id = ids;
                    selectData.name = names;
                    selectData.obj = res;
                } else {
                    selectData.id = null;
                    selectData.name = null;
                    selectData.obj = null;
                }
            } else {
                var findItem = list.value.find((x) => {
                    return x[props.value] === newVal;
                });

                if (!Gx.IsEmpty(findItem)) {
                    //找到对应项，则说明是选择的内容
                    selectData.id = findItem[props.value];
                    selectData.name = findItem[props.label];
                    selectData.obj = findItem;
                    vModelData.value = selectData.id;
                }
            }
        }

        if (!DataSame()) {
            SetEmitData();
        }
    }
);

//监听name，主要是为了处理 allow-create 这个功能，用于处理选择、输入、手动赋值的情况下，保证selectData和vModelData数据正确
//name的监听，还有一个做用，就是在没有使用 allow-create 时，补充了当把id和name都设置为null时功能
watch(
    () => props.name,
    (newVal, oldVal) => {
        //当id和name都没有值的时候，那就认为把所有值都给null
        if (Gx.IsEmpty(props.id) && Gx.IsEmpty(newVal)) {
            selectData.id = null;
            selectData.name = null;
            selectData.obj = null;
            vModelData.value = null;
        }

        //当id没值，name有值时，则是为了补充上面id监听时，单选情况下 findItem 为空的情况
        if (Gx.IsEmpty(props.id) && !Gx.IsEmpty(newVal)) {
            selectData.id = null;
            selectData.name = newVal;
            selectData.obj = null;
            vModelData.value = newVal;
        }

        if (!DataSame()) {
            SetEmitData();
        }
    }
);

//加载事件
onMounted(() => {
    if (_multiple && _allowCreate) {
        throw new Error("DataSelect组件不能同时存在 multiple 和 allow-create 属性！");
    }

    vModelData.value = props.id;

    //对于没有id，但是有name的时候，这里需要处理回显
    if (Gx.IsEmpty(props.id) && !Gx.IsEmpty(props.name)) {
        vModelData.value = props.name;
    }

    if (props.isLoadingData == true) {
        GetList();
    }
});

//得到数据
const GetList = (): Promise<any> => {
    //通过属性来判断api的对象是函数还是非函数
    let api: any = Api[props.table][props.action];

    if (props.actionIsFunc) {
        api = Gx.IsEmpty(props.orderBy) ? api() : api(props.orderBy);
    }
    
    //调用接口拿数据
    return App.CallApi(api, {
        data: props.filter,
        success: (res) => {
            if (props.distinct == true) {
                let temp = _.uniqBy(res, function (obj) {
                    return obj[props.value];
                });
                list.value = temp;
            } else {
                list.value = res;
            }
            
            const currentId = vModelData.value
            if (currentId != null) {
                const found = list.value.find(item => item[props.value] === currentId)
                if (!found) {
                  ClearData() // 如果不在新数据中，就清空当前选择项
                }            
            }
        },
    });
};

//下拉列表框改变事件
const onChange = (val: any) => {
    SetSelectData(val);
};

//下拉列表失去焦点
const onBlur = (event: FocusEvent) => {
    let val = (event?.currentTarget as any)?.value;
    if (!Gx.IsEmpty(val)) {
        SetSelectData(val);
    }
};

//下拉列表清空
const onClear = () => {
    ClearData();
    emit("change");
};

//清空当前组件中的数据，多用于在父组件中调用GetList后用来清空数据用的
const ClearData = () => {
    selectData.id = null;
    selectData.name = null;
    selectData.obj = null;
    vModelData.value = null;

    SetEmitData();
};

//下拉列表隐藏事件
const onVisibleChange = (isShow: boolean) => {
    if (isShow == false) {
        if (DataSame()) {
            //如果数据相同，则直接返回
            return;
        }

        if (Gx.IsEmpty(selectData.id) && !Gx.IsEmpty(selectData.name) && _allowCreate) {
            //如果id没值且name有值且控制可创建条目，则可以认为name中保存的就是输入的内容，则需要把控件的v-model改变一下，否则界面显示不出来
            vModelData.value = selectData.name;
        }

        SetEmitData();
        emit("change");
    }
};

//设置数据
const SetSelectData = (val) => {
    selectData.id = null;
    selectData.name = null;
    selectData.obj = null;

    if (_allowCreate) {
        //可创建条目

        var findItem = list.value.find((x) => {
            return x[props.value] === val;
        });

        if (Gx.IsEmpty(findItem)) {
            //找不到对应项，则说明是输入的内容
            selectData.name = val;
        } else {
            //找到对应项，则说明是选择的内容
            selectData.id = findItem[props.value];
            selectData.name = findItem[props.label];
            selectData.obj = findItem;
        }
    } else {
        if (_multiple) {
            //多选
            let res = [];

            vModelData.value?.forEach((x) => {
                var findItem = list.value.find((item) => {
                    return item[props.value] === x;
                });

                if (!Gx.IsEmpty(findItem)) {
                    res.push(findItem);
                }
            });

            if (res.length > 0) {
                let ids = res.map((x) => {
                    return x[props.value];
                });
                let names = res.map((x) => {
                    return x[props.label];
                });

                selectData.id = ids;
                selectData.name = names;
                selectData.obj = res;
            }
        } else {
            //单选
            var findItem = list.value.find((x) => {
                return x[props.value] === vModelData.value;
            });

            if (!Gx.IsEmpty(findItem)) {
                selectData.id = findItem[props.value];
                selectData.name = findItem[props.label];
                selectData.obj = findItem;
            }
        }
    }
};

//2个数量是否相同
const DataSame = () => {
    let obj1 = Gx.DeepCopy(_oldSelectData);
    let obj2 = Gx.DeepCopy(selectData);

    if (Gx.IsEmpty(obj1) || Gx.IsEmpty(obj2)) {
        return;
    }

    //在这里删除obj，是为了怕obj里的数据有不同而导致比较不相同
    delete obj1.obj;
    delete obj2.obj;

    return Gx.SerializeObject(obj1) == Gx.SerializeObject(obj2);
};

//更新v-model相关数据，并记录到旧值
const SetEmitData = () => {
    emit("update:id", selectData.id);
    emit("update:name", selectData.name);
    emit("update:obj", selectData.obj);

    _oldSelectData = Gx.DeepCopy(selectData);
};

const ClearList = () => {
    list.value = [];
};

//设置下拉列表的值
const SetList = (arr) => {
    list.value = Gx.DeepCopy(arr);
};

const emit = defineEmits(["update:id", "update:name", "update:obj", "change"]);
defineExpose({ GetList, ClearData, ClearList, SetList });
</script>
